// Package pay_api provides a unified interface for integrating with third-party payment APIs.
// It implements HTTP client functionality using GoFrame's g.Client() for reliable API communication.
package pay_api

import (
	"context"
	"fmt"
	"time"

	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/gclient"
	"github.com/gogf/gf/v2/os/glog"
)

// ClientInterface defines the interface that both real and mock clients must implement
type ClientInterface interface {
	DoRequest(ctx context.Context, method, endpoint string, data any) (*gjson.Json, error)
}

// Client represents a payment API client that implements the common API interface.
type Client struct {
	config *Config
	client *gclient.Client
	logger *glog.Logger
}

// Config holds the configuration for the payment API client.
type Config struct {
	BaseURL   string        `json:"base_url"`   // API base URL
	Username  string        `json:"username"`   // API username
	Key       string        `json:"key"`        // API key
	Timeout   time.Duration `json:"timeout"`    // Request timeout
	UserAgent string        `json:"user_agent"` // Custom user agent
}

// NewClient creates a new payment API client with the provided configuration.
func NewClient(config *Config) (*Client, error) {
	if config == nil {
		return nil, NewPayAPIError(ErrInvalidConfig, "config cannot be nil")
	}

	if err := config.Validate(); err != nil {
		return nil, err
	}

	// Create HTTP client using GoFrame's g.Client() with timeout
	client := g.Client().Timeout(config.Timeout)

	// Set default headers
	client.SetHeader("User-Agent", config.UserAgent)
	client.SetHeader("Content-Type", "application/json")

	return &Client{
		config: config,
		client: client,
		logger: g.Log(),
	}, nil
}

// Name returns the client name for identification.
func (c *Client) Name() string {
	return "pay_api"
}

// GetConfig returns the current client configuration.
func (c *Client) GetConfig() *Config {
	return c.config
}

// Close performs cleanup operations for the client.
func (c *Client) Close() error {
	// Cleanup resources if needed
	c.logger.Info(context.Background(), "Payment API client closed")
	return nil
}

// Ping tests the connection to the payment API.
func (c *Client) Ping(ctx context.Context) error {
	// Create a copy of the client for this request
	reqClient := c.client.Clone()

	// Perform a simple health check request
	resp, err := reqClient.Get(ctx, c.config.BaseURL+"/health")
	if err != nil {
		return NewPayAPIError(ErrConnectionFailed, fmt.Sprintf("ping failed: %v", err))
	}
	defer resp.Close()

	if resp.StatusCode != 200 {
		return NewPayAPIError(ErrAPIError, fmt.Sprintf("ping returned status %d", resp.StatusCode))
	}

	return nil
}

// DoRequest performs a generic HTTP request with proper error handling.
func (c *Client) DoRequest(ctx context.Context, method, endpoint string, data interface{}) (*gjson.Json, error) {
	// Create a copy of the client for this request
	reqClient := c.client.Clone()

	// Set authentication headers
	reqClient.SetHeaderMap(g.MapStrStr{
		"grpc-app-username": c.config.Username,
		"grpc-app-key":      c.config.Key,
	})

	url := c.config.BaseURL + endpoint
	c.logger.Debugf(ctx, "Making %s request to: %s", method, url)

	var resp *gclient.Response
	var err error

	// Handle different HTTP methods
	switch method {
	case "GET":
		resp, err = reqClient.Get(ctx, url)
	case "POST":
		resp, err = reqClient.ContentJson().Post(ctx, url, data)
	case "PUT":
		resp, err = reqClient.ContentJson().Put(ctx, url, data)
	case "DELETE":
		resp, err = reqClient.Delete(ctx, url)
	default:
		return nil, NewPayAPIError(ErrInvalidRequest, fmt.Sprintf("unsupported HTTP method: %s", method))
	}

	if err != nil {
		c.logger.Errorf(ctx, "Request failed: %v", err)
		return nil, NewPayAPIError(ErrConnectionFailed, fmt.Sprintf("request failed: %v", err))
	}

	c.logger.Debugf(ctx, "Raw:\n%s", resp.Raw())
	defer resp.Close()

	// Check HTTP status code
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body := resp.ReadAllString()
		c.logger.Errorf(ctx, "API error - Status: %d, Body: %s", resp.StatusCode, body)
		return nil, NewPayAPIError(ErrAPIError, fmt.Sprintf("API returned status %d: %s", resp.StatusCode, body))
	}

	// Read and parse response
	body := resp.ReadAllString()

	// Parse JSON response
	jsonResp := gjson.New(body)
	if jsonResp == nil {
		return nil, NewPayAPIError(ErrResponseParsing, "failed to parse JSON response")
	}

	c.logger.Debugf(ctx, "Request successful - Status: %d", resp.StatusCode)
	return jsonResp, nil
}

// Validate validates the configuration.
func (c *Config) Validate() error {
	if c.BaseURL == "" {
		return NewPayAPIError(ErrInvalidConfig, "base URL is required")
	}

	if c.Username == "" {
		return NewPayAPIError(ErrInvalidConfig, "username is required")
	}

	if c.Key == "" {
		return NewPayAPIError(ErrInvalidConfig, "key is required")
	}

	if c.Timeout <= 0 {
		c.Timeout = 30 * time.Second // Default timeout
	}

	if c.UserAgent == "" {
		c.UserAgent = "Toolkit-PayAPI-Client/1.0" // Default user agent
	}

	return nil
}

// DefaultConfig returns a default configuration for the payment API client.
func DefaultConfig() *Config {
	return &Config{
		BaseURL:   "https://pay.space-iot.net/api/grpcProxy/",
		Timeout:   30 * time.Second,
		UserAgent: "Toolkit-PayAPI-Client/1.0",
	}
}
